/*
 * CHttpTokenAuth.cpp
 *
 *  Created on: 2018年2月9日
 *      Author: terry
 */

#include "CHttpTokenAuth.h"
#include <functional>
#include "TStringCast.h"
#include <time.h>
#include <algorithm>

namespace impl
{

struct CheckUpdateExpiration : public std::binary_function<std::string, CHttpTokenAuth::TokenEntry,bool>
{
	explicit CheckUpdateExpiration(size_t expiration):
		m_expiration(expiration)
	{
	}

	bool operator ()(const std::string& key, CHttpTokenAuth::TokenEntry& entry)
	{
		bool checked = false;
		time_t curTime = time(NULL);
		if (entry.expiration >= curTime)
		{
			entry.expiration = curTime + m_expiration;
			checked = true;
		}
		return checked;
	}

	size_t m_expiration;
};

struct IsExpired : public std::binary_function<std::string, CHttpTokenAuth::TokenEntry,bool>
{
	explicit IsExpired(time_t curTime):
		m_curTime(curTime)
	{
	}

	bool operator ()(const std::string& key, CHttpTokenAuth::TokenEntry& entry)
	{
		bool checked = false;
		return (entry.expiration < m_curTime);
	}

	time_t m_curTime;
};

}

CHttpTokenAuth::CHttpTokenAuth():
	m_expiration(EXPIRATION),
	m_seq(0),
	m_checkTime()
{
}

CHttpTokenAuth::~CHttpTokenAuth()
{
}

size_t CHttpTokenAuth::getExpiration()
{
	return m_expiration;
}

void CHttpTokenAuth::setExpiration(size_t seconds)
{
	m_expiration = seconds;
}

void CHttpTokenAuth::setUserStore(HttpUserStorePtr store)
{
	m_store = store;
}

bool CHttpTokenAuth::login(const std::string& username, const std::string& password, std::string& token)
{
	if (!m_store)
	{
		return false;
	}

	HttpUser user;
	if (!m_store->findUser(username, user))
	{
		return false;
	}

	bool touched = false;
	if (token.size() > 0)
	{
		//refresh
		auto handler = [&](TokenEntry& entry)
		{
			entry.expiration = time(NULL) + m_expiration;
		};
		touched = m_tokenMap.findDo(token, handler);
	}

	if (!touched)
	{
		if (user.password != password)
		{
			return false;
		}

		unsigned int rand = time(NULL) << 8;
		unsigned int num = ++m_seq + rand;
		token = comn::StringCast::toString(num);

		TokenEntry entry;
		entry.username = username;
		entry.token = token;
		entry.expiration = time(NULL) + m_expiration;
		entry.info.id = user.id;
		entry.info.username = username;

		m_tokenMap.put(token, entry);
	}

	fireUserStatus(token, user.id, username, true);

	return true;
}

void CHttpTokenAuth::logout(const std::string& token)
{
	TokenEntry entry;
	bool got = m_tokenMap.remove(token, entry);
	if (got)
	{
		fireUserStatus(token, entry.info.id, entry.username, false);
	}
}

bool CHttpTokenAuth::check(const std::string& token, std::string& username)
{
	time_t curTime = time(NULL);
	if ((curTime - m_checkTime) > (m_expiration * 2))
	{
		clearExpired();
	}

	impl::CheckUpdateExpiration checker(m_expiration);
	TokenEntry entry;
	int state = m_tokenMap.checkGetRemove(token, entry, checker);
	if (state == TokenMap::kTrue)
	{
		username = entry.username;
	}
	else if (state == TokenMap::kNone)
	{
		fireUserStatus(token, entry.info.id, entry.username, false);
	}
	return (state == TokenMap::kTrue);
}

void CHttpTokenAuth::clear()
{
	m_tokenMap.clear();
}

size_t CHttpTokenAuth::size()
{
	return m_tokenMap.size();
}

bool CHttpTokenAuth::getAt(size_t idx, std::string& username, std::string& token)
{
	TokenEntry entry;
	bool found = m_tokenMap.getAt(idx, token, entry);
	if (found)
	{
		username = entry.username;
	}
	return found;
}

void CHttpTokenAuth::clearExpired()
{
	m_checkTime = time(nullptr);

	impl::IsExpired func(time(NULL));
	std::vector<TokenEntry> entryList;
	m_tokenMap.removeAll(func, entryList);

	for (const TokenEntry& entry : entryList)
	{
		fireUserStatus(entry.token, entry.info.id, entry.username, 0);
	}
}

bool CHttpTokenAuth::putUserInfo(const std::string& token, const HttpUserInfo& info)
{
	auto handler = [&](TokenEntry& entry)
	{
		entry.info = info;
	};
	return m_tokenMap.findDo(token, handler);
}

bool CHttpTokenAuth::getUserInfo(const std::string& token, HttpUserInfo& info)
{
	TokenEntry entry;
	bool found = m_tokenMap.find(token, entry);
	if (found)
	{
		info = entry.info;
	}
	return found;
}

bool CHttpTokenAuth::putToken(const std::string& token, const std::string& username, int expiration)
{
	bool got = false;
	auto handler = [&](TokenEntry& entry)
	{
		entry.username = username;
		entry.expiration = time(nullptr) + expiration;
	};

	if (!m_tokenMap.findDo(token, handler))
	{
		TokenEntry entry;
		entry.username = username;
		entry.token = token;
		entry.expiration = time(NULL) + m_expiration;
		m_tokenMap.put(token, entry);
		got = true;
	}

	return got;
}

bool CHttpTokenAuth::touch(const std::string& token)
{
	auto handler = [&](TokenEntry& entry)
	{
		entry.expiration = time(nullptr) + m_expiration;
	};
	return m_tokenMap.findDo(token, handler);
}

size_t CHttpTokenAuth::listUser(std::vector<std::string>& users)
{
	size_t count = 0;
	impl::IsExpired isExpired(time(NULL));

	auto pred = [&](const std::string& k, TokenEntry& entry)
	{
		if (isExpired(k, entry))
		{
			return true;
		}

		if (append(users, entry.username))
		{
			count++;
		}

		return true;
	};
	m_tokenMap.forEach(pred);
	return count;
}

void CHttpTokenAuth::setUserStatusListener(UserStatusListener listener)
{
	m_statusListener = listener;
}

void CHttpTokenAuth::checkExpired()
{
	clearExpired();
}

void CHttpTokenAuth::fireUserStatus(const std::string& token, int uid, const std::string& username, int status)
{
	if (m_statusListener)
	{
		m_statusListener(token, uid, username, status);
	}
}

bool CHttpTokenAuth::append(std::vector<std::string>& users, const std::string& user)
{
	bool changed = false;
	auto it = std::find(users.begin(), users.end(), user);
	if (it == users.end())
	{
		users.push_back(user);
		changed = true;
	}
	return changed;
}